import { SearchOutlined, SwapRightOutlined } from '@ant-design/icons';
import { Input as AntInput, InputNumber as AntInputNumber, Space } from 'antd';
import type { InputProps, PasswordProps, SearchProps, TextAreaProps } from 'antd/es/input';
import type { InputNumberProps } from 'antd/es/input-number';
import useCursor from 'rc-input-number/es/hooks/useCursor';
import React from 'react';
import { Tooltip } from '../functionalComponent';
import { compHoc, getGlobalConfig, iup, IUPComponent, TypeExtends, useExtendRef, useLayoutUpdateEffect } from '../util';

function getInput(inputComponent, trigger = false) {
  return class extends IUPComponent {
    private parentEl: any = null;
    private inputEl: any = null;
    private Comp = inputComponent;
    lastInputTxt = '';

    get editable() {
      return this.props?.editable ?? true;
    }

    constructor(props) {
      super(props);
      this.state.value = props.defaultValue;
      if (this.props.type === 'number' && this.Comp !== AntInputNumber) {
        this.Comp = AntInputNumber;
      }
    }

    static getDerivedStateFromProps(nextProps, prevState) {
      return IUPComponent.propsToState(nextProps, prevState, ['value']);
    }

    setValue(value, cb?) {
      if (this.state.value === value) {
        return;
      }
      this.setState({ value }, () => {
        this.props.onChange?.(value);
        cb?.(value);
      });
    }

    getValue() {
      return this.state.value;
    }

    onInputChange = (e) => {
      const currentValue = e && e.target ? e.target.value : e;
      let value = currentValue;
      if (this.Comp !== AntInputNumber && this.props.parser) {
        value = this.props.parser(value);
      }
      if (this.props.hasOwnProperty('value')) {
        // 外部受控
        this.state.value = value;
        this.props.onChange?.(value);
      } else {
        this.setValue(value);
      }
      this.innerNotify([value], 'onChange').then();
    };

    /**
     * 过滤多余的属性
     * @param inputProps
     * @param type input类型
     */
    getInputProps = (inputProps, type) => {
      inputProps.allowClear = inputProps.allowClear ?? true;

      if (this.Comp === AntInputNumber) {
        delete inputProps.allowClear;
        const precision = this.getPrecision(true);
        inputProps.precision = this.getPrecision();
        inputProps.controls = false;
        if (!inputProps.formatter && !inputProps.parser) {
          if (type === 'rate') {
            inputProps.formatter = (value) => {
              return iup.thousandNumber(iup.numberPrecision(value * 100), {
                precision: iup.getCursorPosition() > -1 ? undefined : precision
              });
            };
            inputProps.parser = (value) => {
              const v = +value!.replace(/\$\s?|(,*)/g, '');
              return iup.isNumber(precision)
                ? +iup.numberPrecision(v / 100).toFixed(precision + 2)
                : iup.numberPrecision(v / 100);
            };
          } else {
            inputProps.formatter = (value) => {
              return iup.thousandNumber(value, {
                precision: iup.getCursorPosition() > -1 ? undefined : precision
              });
            };
            inputProps.parser = (value) => value!.replace(/\$\s?|(,*)/g, '');
          }
        }
      } else if (type) {
        inputProps.type = type;
      }
      if (this.Comp !== AntInputNumber) {
        delete inputProps.formatter;
        delete inputProps.parser;
      }
      if (this.Comp === AntInput.Password) {
        if (inputProps.disabled) {
          inputProps.visibilityToggle = false;
        }
      }

      if (trigger) {
        delete inputProps.onTrigger;
        delete inputProps.editable;
      }
      if (inputProps.disabled) {
        delete inputProps.allowClear;
        inputProps.placeholder = '';
      }
      if (inputProps.maxLength && !inputProps.count) {
        const max = inputProps.maxLength;
        inputProps.count = {
          show: false,
          max,
          strategy: (txt) => {
            if (iup.strLen(txt) <= max) {
              this.lastInputTxt = txt;
            } else {
              let tmp = txt.substring(0, max);
              while (tmp && iup.strLen(tmp) > max) {
                tmp = tmp.substring(0, tmp.length - 1);
              }
              this.lastInputTxt = tmp;
            }
            return iup.strLen(txt);
          },
          exceedFormatter: () => {
            return this.lastInputTxt;
          }
        };
        delete inputProps.maxLength;
      }
      return inputProps;
    };

    elClick = (e) => {
      if (this.props.disabled) {
        return;
      }
      if (this.editable) {
        if (
          !iup.closest(e.target, (el) => el.classList.contains('ant-input-clear-icon')) &&
          iup.closest(e.target, (el) => el.classList.contains('ant-input-suffix'))
        ) {
          this.props.onTrigger?.(e);
        }
      } else if (!iup.closest(e.target, (el) => el.classList.contains('ant-input-clear-icon'))) {
        this.props.onTrigger?.(e);
      }
    };

    dbClick = (e) => {
      if (this.props.disabled) {
        return;
      }
      this.props.onTrigger?.(e);
    };

    getPrecision(calc = false) {
      const { type, precision } = this.props;
      if (
        ['percent', 'prc', 'amt', 'amount', 'qty', 'rate'].includes(type) &&
        iup.isNumber(getGlobalConfig().default.precision[type])
      ) {
        const p = getGlobalConfig().default.precision[type];
        if (calc && type === 'rate') {
          return Math.max(0, p - 2);
        }
        return p;
      }
      if (
        !iup.isNullOrEmpty(precision) &&
        ['number', 'prc', 'amt', 'amount', 'qty', 'rate', 'percent'].includes(type)
      ) {
        const tmp = Number(precision);
        return iup.isNumber(tmp) ? (type === 'rate' && calc ? Math.max(0, tmp - 2) : tmp) : undefined;
      }
      return precision;
    }

    componentDidMount(): void {
      this.inputEl = this.outRef.current?.input || this.outRef.current;
      if (trigger) {
        this.parentEl = this.inputEl?.parentElement || this.inputEl || this.outRef.current;
        this.parentEl.addEventListener('click', this.elClick);
        if (this.editable && this.inputEl) {
          this.inputEl.addEventListener('dblclick', this.dbClick);
        }
      }
    }

    componentWillUnmount(): void {
      if (trigger) {
        this.parentEl?.removeEventListener('click', this.elClick);
        if (this.editable) {
          this.inputEl?.removeEventListener('dblclick', this.dbClick);
        }
      }
    }

    getTooltipRender = (title, tooltip) => () => {
      if (iup.isBoolean(tooltip)) {
        return title;
      } else if (iup.isFunction(tooltip)) {
        return tooltip(title);
      }
      return tooltip;
    };

    getFormatValue = (type) => {
      return this.props.formatter && this.Comp !== AntInputNumber
        ? this.props.formatter(this.state.value || '')
        : this.state.value;
    };

    render() {
      const Comp = this.Comp;
      const {
        value,
        onChange,
        suffix,
        style = {},
        editable,
        className,
        type = Comp === AntInputNumber ? 'number' : 'text',
        tooltip,
        observer,
        enterButton,
        id,
        ...inputProps
      } = this.props as any;
      const suffixIcon =
        suffix ||
        (trigger && !inputProps.disabled ? (
          <SearchOutlined
            style={{
              cursor: 'pointer',
              opacity: 0.45,
              fontSize: 14
            }}
          />
        ) : null);
      const formatValue = this.getFormatValue(type);
      const innerProps = this.getInputProps(inputProps, type);
      if (Comp === AntInput.Search && !enterButton && !inputProps.disabled) {
        innerProps.suffix = formatValue ? (
          <></>
        ) : (
          <SearchOutlined style={{ cursor: 'pointer', fontSize: 14, opacity: 0.45 }} />
        );
        innerProps.enterButton = null;
        innerProps.className = iup.classNames(className, `iup-${type}`, 'hidden-addon-icon', {
          'iup-trigger-input': trigger && editable === false
        });
      }
      const renderInput = ({ recordCursor }: any = {}) => (
        <Comp
          ref={this.outRef}
          className={iup.classNames(className, `iup-${type}`, { 'iup-trigger-input': trigger && editable === false })}
          value={formatValue}
          style={{ width: '100%', ...style }}
          suffix={suffixIcon}
          {...innerProps}
          onChange={(e) => {
            recordCursor?.();
            this.onInputChange(e);
          }}
        />
      );
      if ([AntInput].includes(Comp) && this.props.formatter) {
        return <InnerInput renderInput={renderInput} value={formatValue} ins={this} />;
      }
      return tooltip ? (
        <Tooltip title={this.getTooltipRender(formatValue || innerProps.placeholder, tooltip)}>{renderInput()}</Tooltip>
      ) : (
        renderInput()
      );
    }
  };
}

function InnerInput({ renderInput, value, ins }) {
  const { placeholder, tooltip, formatter } = ins.props;

  const [recordCursor, restoreCursor] = useCursor(ins.outRef.current?.input, true);

  useLayoutUpdateEffect(() => {
    formatter && value && restoreCursor();
  }, [value]);

  return tooltip ? (
    <Tooltip title={ins.getTooltipRender(value || placeholder, tooltip)}>{renderInput({ recordCursor })}</Tooltip>
  ) : (
    renderInput({ recordCursor })
  );
}

type TypeProps<T> = TypeExtends<
  T,
  { onChange?: (value: any) => void; tooltip?: true | string | ((value) => React.ReactNode) }
>;

export const Input = compHoc<
  TypeProps<InputProps & { formatter?: (value: string) => string; parser?: (formatValue: string) => string }>
>(getInput(AntInput));

// prc 是单价 amt是合价 qty是工程量，rate是百分比
export const InputNumber = compHoc<
  TypeProps<InputNumberProps & { type?: 'number' | 'amount' | 'percent' | 'prc' | 'amt' | 'qty' | 'rate' }>
>(getInput(AntInputNumber));
export const TextArea = compHoc<TypeProps<TextAreaProps>>(getInput(AntInput.TextArea));
export const Password = compHoc<TypeProps<PasswordProps>>(getInput(AntInput.Password));
export const Search = compHoc<TypeProps<SearchProps>>(getInput(AntInput.Search));

export const InputTrigger = compHoc<InputProps & { onTrigger?: Function; editable?: boolean }>(
  getInput(AntInput, true)
);

export const InputNumberRange = compHoc<any>(
  ({ outRef, style, placeholder, value, onChange, allowClear, ...props }) => {
    const getValueArr = () => {
      const tmp: any = iup.split(value);
      tmp[0] = tmp[0] ? +tmp[0] : null;
      tmp[1] = tmp[1] ? +tmp[1] : null;
      if (!iup.isNumber(tmp[0])) {
        tmp[0] = null;
      }
      if (!iup.isNumber(tmp[1])) {
        tmp[1] = null;
      }
      return tmp;
    };

    const placeholderArr = iup.split(placeholder);
    const vauleArr = getValueArr();
    const handleChange = (idx) => (v) => {
      const newValue = [vauleArr[0], vauleArr[1]];
      newValue[idx] = v;
      onChange?.(newValue);
    };

    useExtendRef(outRef, () => {
      return {
        getValue() {
          return getValueArr();
        },
        setValue(v) {
          onChange?.(v);
        }
      };
    });

    return (
      <Space.Compact block style={style} className="ant-input-number ant-input-number-outlined">
        <AntInputNumber
          {...props}
          value={vauleArr[0]}
          onChange={handleChange(0)}
          controls={false}
          style={{ flex: 1, width: 0, border: 0 }}
          placeholder={placeholderArr[0]}
        />
        <SwapRightOutlined />
        <AntInputNumber
          {...props}
          controls={false}
          onChange={handleChange(1)}
          style={{
            flex: 1,
            width: 0,
            border: 0
          }}
          value={vauleArr[1]}
          placeholder={placeholderArr[1]}
        />
      </Space.Compact>
    );
  }
);
